#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the version 3 of the GNU Lesser General Public License
#   as published by the Free Software Foundation.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU Lesser General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Copyright (c) NEC Deutschland GmbH, NEC HPC Europe
#
# $Id: group_max_cycle.py 8 2011-03-24 14:40:41Z efocht@gmail.com $

from copy import copy
import logging
from aggmon.dp_aggregator import DPAggregator, DPAggregatorComponent
from basic_types.metric import Metric


class GroupSumCycleAggregator(DPAggregator):
    """
    Provides running sum of a particular metric for the group
    for a cycle of reported metrics, i.e. the aggregator will wait until
    all group members have reported the metric it is waiting for, publish
    the summed value and then re-initialize the counters for a new cycle.
    This aggregator makes no sense without a hierarchy!
    
    Arguments:
       metric_name : should match a metric that is published inside the group
       agg_metric_name : name of new (aggregated) metric
       group_path  : group hierarchy path (Example: '/siteB/rack12')
    """
    def __init__( self, metric_name, agg_metric_name, group_path, max_age=900, **kwds ):
        DPAggregator.__init__( self, metric_name, agg_metric_name, group_path, **kwds )
        assert self.getHierarchy() is not None, "Need a hierarchy for cyclic group aggregators"
        
        # access these only when holding the lock
        self.max_age = max_age
        self.sum_value = None
        self.last_time = None
        group = self.getHierarchy().get_groups( group_path )[0]
        self.known_members = dict((key, None) for key in group.get_member_short_names() )
        self.expected_members = copy( self.known_members )

    def handleEvent( self, metric, topic=None ):
        if self.worthy_metric( metric, topic ):
            value = metric.value
        else:
            return
        if not isinstance( value, ( int, long, float ) ):
            return

        params = {}
        self._lock.acquire()

        try:
            # can be a child host or a child group name        
            topic_host = topic.split(".")[-3]
            if topic_host in self.expected_members:
                del self.expected_members[topic_host]
            else:
                if not topic_host in self.known_members:
                    # didn't expect this guy, ignore it and return
                    logging.info( "unexpected topic_host in GroupSumCycleAggregator: %s" % topic_host )
                    return

            if self.sum_value is None:
                self.sum_value = value
                self.last_time = metric.time
            elif self.sum_value is not None:
                self.sum_value = self.sum_value + value

            if len( self.expected_members.keys() ) == 0 \
                or (metric.time - self.last_time > self.max_age):
                #go_publish

                params = { "name": self.agg_metric_name,
                           "value": self.sum_value,
                           "source": "aggregator",
                           "time":  metric.time,
                           "host": self._group_topic }
                # initialize for next round
                self.sum_value = None
                self.last_time = metric.time
                self.expected_members = copy( self.known_members )

        finally:
            self._lock.release()

        if len( params ) > 0:
            sum_metric = Metric( **params )
            self.publish( sum_metric )


class GroupSumCycleAggregatorComponent(DPAggregatorComponent):
    def __init__( self, *args, **kwds ):
        DPAggregatorComponent.__init__( self, *args, **kwds)
        self.allowed_kwds.extend( ["max_age"] )

    def instantiate( self, *args, **kwds ):
        self.instance = None
        try:
            logging.debug( "instantiating: %s" % self.name )
            self.instance = GroupSumCycleAggregator( *args, **kwds )
        except Exception, e:
            raise e
